package com.apobates.jforum.grief.aurl.core;

import com.apobates.jforum.grief.aurl.convertor.*;
import com.apobates.jforum.grief.aurl.executor.AUrlMetaExecutor;
import com.apobates.jforum.grief.aurl.executor.ClipParameterExecutor;
import com.apobates.jforum.grief.aurl.executor.ResolveExecutor;
import com.apobates.jforum.grief.aurl.selector.*;
import com.apobates.jforum.grief.aurl.parser.PatternParser;
import com.apobates.jforum.grief.aurl.utils.CurlyBraceSymbol;
import com.apobates.jforum.grief.aurl.spring.StringUtils;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.*;
import java.util.*;
import java.util.function.Function;

/**
 * URL的原生信息类
 */
public final class AUrlMeta implements Serializable,Cloneable {
    private final String scheme;
    private final String host;
    private final String port;
    private final String fragment;
    private final String path;
    private final List<String> pathSegments;
    private final Map<String, List<String>> queryParams;
    //
    private final String ssp;
    private final String userInfo;
    private static final char PATH_DELIMITER = '/';
    private static final String PATH_DELIMITER_STR=String.valueOf(PATH_DELIMITER);


    public AUrlMeta(AUrlMetaBuilder builder){
        this.scheme = builder.getScheme();
        this.host = builder.getHost();
        this.port = builder.getPort();
        this.fragment = builder.getFragment();
        this.ssp = builder.getSsp();
        this.userInfo = builder.getUserInfo();
        this.queryParams = builder.getQueryParams();
        this.path = builder.getPath();
        this.pathSegments = builder.getPathSegments();
    }

    /**
     * 返回协议模式
     * 示例: http://www.a.com:8080/order/create?product=100#descript ===> http
     * @return
     */
    public String getScheme() {
        return scheme;
    }

    /**
     * 返回主机名称
     * 示例: http://www.a.com:8080/order/create?product=100#descript ===> www.a.com
     * @return
     */
    public String getHost() {
        return host;
    }

    /**
     * 返回端口号
     * 示例: http://www.a.com:8080/order/create?product=100#descript ===> 8080
     * @return
     */
    public String getPort() {
        return port;
    }

    /**
     * 返回片段
     * 示例: http://www.a.com:8080/order/create?product=100#descript ===> descript
     * @return
     */
    public String getFragment() {
        return fragment;
    }

    /**
     * 返回查询参数的Map结构
     * @return
     */
    public Map<String, List<String>> getQueryParams() {
        return queryParams;
    }

    /**
     * 返回查询字符串
     * 示例: http://www.a.com:8080/order/create?product=100#descript ===> product=100
     * @return
     */
    public String getQuery() {
        return getQuery(null);
    }

    /**
     * 返回查询字符串
     * 示例: http://www.a.com:8080/order/create?product=100#descript ===> product=100
     * @param defValue 若不可用时使用的默认值
     * @return
     */
    public String getQuery(String defValue){
        QueryStringSelector qss = this.select(QueryStringSelector.class);
        return qss.getSingle(()->defValue);
    }

    /**
     * 查看指定查询参数的值. 只取第一个值
     * @param paramName 参数名称
     * @return 若不存在参数名称或无值包装值为null
     */
    public Optional<String> getQueryParam(String paramName){
        String temp = null;
        if(queryParams.containsKey(paramName)){
            temp = queryParams.get(paramName).get(0);
        }
        return Optional.ofNullable(temp);
    }

    /**
     * 查看指定查询参数的值
     * @param paramName 参数名称
     * @return 若不存在参数名称返回一个空列表
     */
    public Collection<String> getQueryParams(String paramName){
        return queryParams.containsKey(paramName)?queryParams.get(paramName): Collections.emptyList();
    }

    /**
     * 返回路径
     * 示例: http://www.a.com:8080/order/create?product=100#descript ===> /order/create
     * @return
     */
    public String getPath(){
        return null == path|| path.isEmpty()?PATH_DELIMITER_STR:path;
    }

    /**
     * 返回用户信息
     * 示例: https://Arnab_Kundu@www.a.com ===> Arnab_Kundu
     * @return
     */
    public String getUserInfo() {
        return userInfo;
    }

    /**
     * 返回: 解码方案特定部分. 只适用于:URI
     * 参看: URI.getSchemeSpecificPart
     * [scheme:]scheme-specific-part[#fragment]
     * 示例: urn:isbn:096139210x ===> isbn:096139210x
     * @return
     */
    public String getSsp() {
        return ssp;
    }

    /**
     * 返回路径片段
     * 示例:
     * @return
     */
    public  Collection<String> getPathSegments(){
        return new ArrayList<>(pathSegments);
    }

    @Override
    public String toString(){
        return this.convert(new URIStringConvertor());
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        AUrlMeta aUrlMeta = (AUrlMeta) o;
        return scheme.equals(aUrlMeta.scheme) &&
                host.equals(aUrlMeta.host) &&
                port.equals(aUrlMeta.port) &&
                fragment.equals(aUrlMeta.fragment) &&
                path.equals(aUrlMeta.path) &&
                ssp.equals(aUrlMeta.ssp) &&
                userInfo.equals(aUrlMeta.userInfo) &&
                queryParams.equals(((AUrlMeta) o).queryParams);
    }

    @Override
    public int hashCode() {
        return Objects.hash(scheme, host, port, fragment, path, ssp, userInfo, queryParams);
    }

    @Override
    public AUrlMeta clone() {
        try {
            AUrlMeta ins = AUrlMetaBuilder.getInstance()
                    .scheme(this.getScheme())
                    .host(this.getHost())
                    .userInfo(this.getUserInfo())
                    .port(this.getPort())
                    .path(this.getPath())
                    .query(this.getQuery())
                    .fragment(this.getFragment())
                    .build();
            return ins;
        }catch (Exception e){
            return null;
        }
    }

    /**
     * 返回: 文件部分的信息. 非URL时返回: /
     * @see java.net.URL getFile
     * @return 示例: http://www.a.com:8080/order/create?product=100#descript ===> /order/create?product=100#descript
     */
    public String getFile(){
        return getFile(null);
    }

    /**
     * 返回: 文件部分的信息. 非URL时返回: /
     * @see java.net.URL getFile
     * @param defValue 若不可用时使用的默认值
     * @return 示例: http://www.a.com:8080/order/create?product=100#descript ===> /order/create?product=100#descript
     */
    public String getFile(String defValue){
        FileSelector fs  = this.select(FileSelector.class);
        return fs.getSingle(()->defValue);
    }

    /**
     * 目标参数URL与当前是否一致。主要检查:协议,主机/域,端口和文件部分
     * 示例:
     *  String a = "http://www.google.com/search?q=java"
     *  String b = "http://www.google.com/search"
     * 认定两者相比较返回true
     * @param urlStr 待检测的目标URL
     * @return true一致.false不一致
     */
    public boolean isSameFile(String urlStr){
        AUrlMeta temp = new PatternParser().parse(urlStr).build();
        return this.getScheme().equals(temp.getScheme()) &&
                this.getHost().equals(temp.getHost()) &&
                this.getPort().equals(temp.getPort()) &&
                this.getPath().equals(temp.getPath());
    }

    /**
     * 是否是加密模式
     * @return true是false不是
     */
    public boolean isHttps(){
        if(null !=this.scheme){
            return this.scheme.toLowerCase().equals("https");
        }
        return false;
    }

    /**
     * 是否是一个合法的URL. 合法的依据: 检查Schema和Host
     * @return true是的/false不是
     */
    public boolean isLegalURL(){
        return isLegalURL(getScheme(), getHost());
    }

    /**
     * 路径中是否存在占位符
     * @return true表示有占位符
     */
    public boolean hasPathVariable(){
        // PathParamSelector pps = this.select(PathParamSelector.class);
        // return !pps.getResult().isEmpty();
        return new CurlyBraceSymbol(getPath()).hasSymbol();
    }

    /**
     * 返回查询字符串中key的数量
     * @return
     */
    public int getQueryParamSize(){
        return queryParams.size();
    }

    /**
     * 使用当前对象创建一个AUrlMetaBuilder
     * @return
     */
    public AUrlMetaBuilder toBuilder(){
        return this.convert(new BuilderConvertor());
    }

    /**
     * 转换为: java.net.URI实例
     * @param encode 是否进行地址编码. true编码/false不进行编码
     * @return
     */
    public URI toURI(boolean encode){
        return this.convert(new URIConvertor(encode));
    }

    /**
     * 转换为: java.net.URL实例
     * @param encode 是否进行地址编码. true编码/false不进行编码
     * @return
     */
    public URL toURL(boolean encode){
        try {
            return this.toURI(encode).toURL();
        }catch (MalformedURLException e){
            throw new IllegalStateException("从URI转成URL失败,原因:"+e.getMessage());
        }
    }

    /**
     * 转换为: java.net.URI实例. 不进行编码
     * @return
     */
    public URI toURI(){
        return toURI(false);
    }

    /**
     * 转换为: Map. 可用的key: schema,host,userInfo,port,path,query,fragment
     * 若key对应的get方法返回null, key不会存在
     * @return
     */
    public Map<String,String> toMap(){
        return this.convert(new MapConvertor());
    }

    /**
     * 将参数地址中缺少的部分使用当前对象相应部份填充
     * 示例:
     *   当前对象的URL:   https://www.google.com/search?q=baeldung
     *   参数字符串的URL: /page
     *   结果输出的URL:   https://www.google.com/page?q=baeldung
     * @param urlStr
     * @return
     * @throws NullPointerException
     */
    public String join(String urlStr){
        return this.link(urlStr).toString();
    }

    /**
     * 将参数地址中缺少的部分使用当前对象相应部份填充
     * 方法是join的别名方法. 只是返回值不同
     * 示例:
     *   当前对象的URL:   https://www.google.com/search?q=baeldung
     *   参数字符串的URL: /page
     *   结果输出的URL:   https://www.google.com/page?q=baeldung
     * @param urlStr
     * @return
     */
    public AUrlMetaBuilder link(String urlStr){
        Objects.requireNonNull(urlStr);
        return new JoinConvertor().convert(this, urlStr);
    }

    /**
     * 使用相对路径对当前信息进行修正
     * @param relativePath 相对路径信息. 要求以: ../或./开头
     * @return
     * @throws IllegalArgumentException 若参数不满足要求(以: ../或./开头)或者参数不可用
     */
    public String relative(String relativePath)throws IllegalArgumentException{
        return this.resolve(relativePath).toString();
    }

    /**
     * 使用相对路径对当前信息进行修正
     * 方法是relative的别名方法. 只是返回值不同
     * @param relativePath 相对路径信息. 要求以: ../或./开头
     * @return
     * @throws IllegalArgumentException
     */
    public AUrlMetaBuilder resolve(String relativePath)throws IllegalArgumentException{
        ResolveExecutor execute = this.execute(ResolveExecutor.class);
        return execute.relative(relativePath).getBuilder();
    }

    /**
     * 对查询字符串进行编辑: 增加,删除和替换
     * @return 返回一个AUrlMetaExecutor的实现类
     * @throws NullPointerException
     */
    public ClipParameterExecutor clipQueryParam() throws NullPointerException{
        return this.execute(ClipParameterExecutor.class);
    }

    /**
     * 使用FileTypeSelector对文件类型进行查询
     * @return 返回一个AUrlMetaSelector的实现类
     */
    public FileTypeSelector fileType() {
        return this.select(FileTypeSelector.class);
    }

    // -------------------------------------> 扩展方法
    /**
     * 执行自定义的选择器
     * @param classIns AUrlMetaSelector的实现类
     * @return
     * @param <S>
     * @throws NullPointerException
     */
    public <S> S select(Class<? extends AUrlMetaSelector> classIns) throws NullPointerException{
        try {
            Constructor<? extends AUrlMetaSelector> declaredConstructor = classIns.getDeclaredConstructor(AUrlMeta.class);
            AUrlMetaSelector instance = declaredConstructor.newInstance(this);
            return (S)instance;
        }catch (NoSuchMethodException|InvocationTargetException|InstantiationException|IllegalAccessException e){
            throw new NullPointerException("参数类实例化失败. 参数:"+classIns.getName());
        }
    }

    /**
     * 执行自定义的执行器
     * @param classIns RawMetaExecutor<T>的实现
     * @return
     * @param <T>
     * @param <S>
     * @throws NullPointerException
     */
    public <T,S> S execute(Class<? extends AUrlMetaExecutor<T>> classIns) throws NullPointerException{
        try {
            Constructor<? extends AUrlMetaExecutor<T>> declaredConstructor = classIns.getDeclaredConstructor(AUrlMeta.class);
            AUrlMetaExecutor<T> instance = declaredConstructor.newInstance(this);
            return (S)instance;
        }catch (NoSuchMethodException|InvocationTargetException|InstantiationException|IllegalAccessException e){
            throw new NullPointerException("参数类实例化失败. 参数:"+classIns.getName());
        }
    }

    /**
     * 使用Function实现对当前实例进行处理
     * @param executor Function实现
     * @return
     * @param <R> 返回类型
     */
    public <R> R execute(Function<AUrlMeta,R> executor){
        return executor.apply(this);
    }

    /**
     * 使用自定义转换器进行转换
     * @param convertor AUrlMetaConvertor转换器实现
     * @return
     * @param <R>
     */
    public <R> R convert(AUrlMetaConvertor<R> convertor){
        return convertor.convert(this);
    }

    // -------------------------------------> 扩展方法
    private static boolean isLegalURL(String scheme, String host){
        return isLegalURLSchema(scheme) && isLegalURLHost(host);
    }

    /**
     * 是否是合法的URL Schema
     * @param schema 要求： 有值 且 以http开头
     * @return 是返回true
     */
    private static boolean isLegalURLSchema(String schema){
        return StringUtils.hasLength(schema) && schema.toLowerCase().startsWith("http");
    }

    /**
     * 是否是合法的URL Domain
     * @param domain 要求： 有值 且 是合法的域名
     * @return
     */
    private static boolean isLegalURLHost(String domain){
        return StringUtils.hasLength(domain) && (domain.toLowerCase().equals("localhost") || PatternParser.isValidDomain(domain));
    }
}
